package main

import (
	"fmt"
	"sort"
)

func main() {
	// map 初始化

	map1 := make(map[string]int)

	// 存储了一个Key为张三的，Value为43的键值对数据。
	map1["age"] = 34

	fmt.Println(map1)

	//此外还有一种使用map字面量的方式创建和初始化map，对于上面的例子，我们可以同等实现。
	map2 := map[string]int{"张三": 43}

	fmt.Println(map2)
	//使用一个大括号进行初始化，键值对通过:分开，如果要同时初始化多个键值对，使用逗号分割。
	dict3 := map[string]int{"张三": 43, "李四": 50}
	fmt.Println("dict3= ", dict3)
	//当然我们可以不指定任何键值对，也就是一个空map。
	dict4 := map[string]int{}
	fmt.Println("dict4= ", dict4)

	//不管怎么样，使用map的字面量创建一定要带上大括号。如果我们要创建一个nil的Map怎么做呢？nil的Map是未初始化的，
	//所以我们可以只声明一个变量，既不能使用map字面量，也不能使用make函数分配内存。
	var dict5 map[string]int
	fmt.Println("dict5= ", dict5)
	//这样就好了，但是这样我们是不能操作存储键值对的，必须要初始化后才可以，比如使用make函数,为其开启一块可以存储数据的内存，也就是初始化。

	var dict6 map[string]int
	dict6 = make(map[string]int)
	dict6["张三"] = 43
	fmt.Println(dict6)

	//Map的键可以是任何值，键的类型可以是内置的类型，也可以是结构类型，但是不管怎么样，这个键可以使用==运算符进行比较，
	//所以像切片、函数以及含有切片的结构类型就不能用于Map的键了，因为他们具有引用的语义，不可比较。
	//对于Map的值来说，就没有什么限制了，切片这种在键里不能用的，完全可以用在值里。

	//=====使用map========

	//Map的使用很简单，和数组切片差不多，数组切片是使用索引，Map是通过键。

	dict := make(map[string]int)
	dict["张三"] = 43

	//以上示例，如果键张三存在，则对其值修改，如果不存在，则新增这个键值对。
	//获取一个Map键的值也很简单，和存储差不多,还是给予上面的例子。
	age := dict["张三"]
	fmt.Println("age=", age)
	//在Go Map中，如果我们获取一个不存在的键的值，也是可以的，返回的是值类型的零值，这样就会导致我们不知道是真的存在一个为零值的键值对呢，
	//还是说这个键值对就不存在。对此，Map为我们提供了检测一个键值对是否存在的方法。

	age1, exists := dict["李四"]
	fmt.Println("age=", age1, "exists=", exists)

	//看这个例子，和获取键的值没有太大区别，只是多了一个返回值。第一个返回值是键的值；第二个返回值标记这个键是否存在，
	//这是一个boolean类型的变量，我们判断它就知道该键是否存在了。这也是Go多值返回的好处。
	//如果我们想删除一个Map中的键值对，可以使用Go内置的delete函数。

	delete(dict, "张三")
	//delete函数接受两个参数，第一个是要操作的Map，第二个是要删除的Map的键。

	//delete函数删除不存在的键也是可以的，只是没有任何作用。
	//想要遍历Map的话，可以使用for range风格的循环，和遍历切片一样。
	dict7 := map[string]int{"张三": 43}
	for key, value := range dict7 {
		fmt.Println(key, value)
	}
	//这里的range 返回两个值，第一个是Map的键，第二个是Map的键对应的值。这里再次强调，这种遍历是无序的，
	//也就是键值对不会按既定的数据出现，如果想安顺序遍历，可以先对Map中的键排序，然后遍历排序好的键，把对应的值取出来，下面看个例子就明白了。

	dict66 := map[string]int{"王五": 60, "张三": 43}
	var names []string
	for name := range dict66 {
		names = append(names, name)
	}
	sort.Strings(names) //排序
	for _, key := range names {
		fmt.Println(key, dict66[key])
	}

	// =============在函数间传递Map

	//函数间传递Map是不会拷贝一个该Map的副本的，也就是说如果一个Map传递给一个函数，该函数对这个Map做了修改，那么这个Map的所有引用，都会感知到这个修改。

	dictF := map[string]int{"王五": 60, "张三": 43}
	modify(dictF)
	fmt.Println(dictF["张三"])
	//这个例子输出的结果是10,也就是说已经被函数给修改了，可以证明传递的并不是一个Map的副本。
	//这个特性和切片是类似的，这样就会更高，因为复制整个Map的代价太大了。

	//map 其实是一个引用类型，而根据 我们所学，make 创建的 map 本质上是一个 *hmap，
	//而我们常用的 map 其实是一个指针，所以我们平时正常写就行了，不需要带 *。在函数传递过程中，
	//只是把原来的 map 这个指针，复制了一下，而子函数中的指针还是指向了原来的内存空间，所以，在子函数中修改一个map，最终会影响到原来的 map。
	dict2 := map[string]int{"key8": 1, "key2": 2, "key3": 3, "key4": 4}
	fmt.Printf("%p, %p\n", dict2, &dict2) //0xc42007a1b0, 0xc42000c030
	changeMap(dict2)
}

func modify(dict map[string]int) {
	dict["张三"] = 10
}

func changeMap(dict map[string]int) {
	fmt.Printf("%p, %p\n", dict, &dict) //0xc42007a1b0, 0xc42000c038
	dict["key1"] = 100
}
